package com.huishu.order.aop;

import com.huishu.commons.annotation.Idempotent;
import com.huishu.commons.exception.BaseException;
import com.huishu.commons.pojo.Result;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 幂等切面
 *
 * @author Ljj
 * @date 2021/4/25
 */
@Slf4j
@Aspect
@Component
@ConditionalOnClass(RedisTemplate.class)
public class IdempotentAspect {
    @Autowired
    private RedisTemplate<String, String> stringRedisTemplate;

    @Autowired
    private HttpServletRequest request;

    /**
     * 切入点,根据自定义Idempotent注解进行切入
     */
    @Pointcut("@annotation(com.huishu.commons.annotation.Idempotent)")
    public void executeIdempotent() {
    }

    @Around("executeIdempotent()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        // 获取幂等注解
        Idempotent idempotent = method.getAnnotation(Idempotent.class);
        // 拼接成key
        StringBuilder idempotentKey = new StringBuilder(idempotent.value());
        String userId = request.getHeader("uid");
        if (userId == null) {
            log.error("API幂等处理获取用户id异常");
            throw new BaseException("API幂等处理获取用户id异常");
        }
        idempotentKey.append(userId);
        // 通过 setnx 确保只有一个接口能够正常访问
        byte[] bytes = idempotentKey.toString().getBytes();
        Boolean success = stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> {
            Expiration expiration = Expiration.from(idempotent.expire(), TimeUnit.MILLISECONDS);
            return connection.set(bytes, bytes, expiration, RedisStringCommands.SetOption.SET_IF_ABSENT);
        });
        if (Objects.equals(success, true)) {
            return joinPoint.proceed();
        } else {
            log.error("API幂等处理, key=" + idempotentKey);
            return Result.fail("请勿重复操作！！");
        }
    }
}
